 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.framework.internal.core;

 import java.io.IOException ;
 import java.net.URL ;
 import java.util.Enumeration ;
 import org.eclipse.osgi.framework.adaptor.*;
 import org.eclipse.osgi.framework.debug.Debug;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.service.resolver.BundleDescription;
 import org.eclipse.osgi.util.NLS;
 import org.osgi.framework.*;

 public class BundleHost extends AbstractBundle {
     /**
      * The BundleLoader proxy; a lightweight object that acts as a proxy
      * to the BundleLoader and allows lazy creation of the BundleLoader object
      */
     private BundleLoaderProxy proxy;

     /** The BundleContext that represents this Bundle and all of its fragments */
     protected BundleContextImpl context;

     /** The List of BundleFragments */
     protected BundleFragment[] fragments;

     public BundleHost(BundleData bundledata, Framework framework) throws BundleException {
         super(bundledata, framework);
         context = null;
         fragments = null;
     }

     /**
      * Load the bundle.
      */
     protected void load() {
         if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
             if ((state & (INSTALLED)) == 0) {
                 Debug.println("Bundle.load called when state != INSTALLED: " + this); //$NON-NLS-1$
 Debug.printStackTrace(new Exception ("Stack trace")); //$NON-NLS-1$
 }
             if (proxy != null) {
                 Debug.println("Bundle.load called when proxy != null: " + this); //$NON-NLS-1$
 Debug.printStackTrace(new Exception ("Stack trace")); //$NON-NLS-1$
 }
         }

         if (framework.isActive()) {
             SecurityManager sm = System.getSecurityManager();

             if (sm != null && framework.permissionAdmin != null) {
                 domain = framework.permissionAdmin.createProtectionDomain(this);
             }

         }
         proxy = null;
     }

     /**
      * Reload from a new bundle.
      * This method must be called while holding the bundles lock.
      *
      * @param newBundle Dummy Bundle which contains new data.
      * @return true if an exported package is "in use". i.e. it has been imported by a bundle
      */
     protected boolean reload(AbstractBundle newBundle) {
         if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
             if ((state & (INSTALLED | RESOLVED)) == 0) {
                 Debug.println("Bundle.reload called when state != INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
 Debug.printStackTrace(new Exception ("Stack trace")); //$NON-NLS-1$
 }
         }

         boolean exporting = false;

         if (framework.isActive()) {
             if (state == RESOLVED) {
                 BundleLoaderProxy curProxy = getLoaderProxy();
                 exporting = curProxy.inUse();
                 if (exporting)
                     // make sure the BundleLoader is created.
 curProxy.getBundleLoader().createClassLoader();
                 else
                     closeBundleLoader(proxy);
                 state = INSTALLED;
                 proxy = null;
                 fragments = null;
             }

         } else {
             /* close the outgoing jarfile */
             try {
                 this.bundledata.close();
             } catch (IOException e) {
                 // Do Nothing
 }
         }
         this.bundledata = newBundle.bundledata;
         this.bundledata.setBundle(this);
         // create a new domain for the bundle because its signers/symbolic-name may have changed
 if (framework.isActive() && System.getSecurityManager() != null && framework.permissionAdmin != null)
             domain = framework.permissionAdmin.createProtectionDomain(this);
         return (exporting);
     }

     /**
      * Refresh the bundle. This is called by Framework.refreshPackages.
      * This method must be called while holding the bundles lock.
      */
     protected void refresh() {
         if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
             if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
                 Debug.println("Bundle.reload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
 Debug.printStackTrace(new Exception ("Stack trace")); //$NON-NLS-1$
 }
         }
         if (state == RESOLVED) {
             closeBundleLoader(proxy);
             proxy = null;
             fragments = null;
             state = INSTALLED;
             // Do not publish UNRESOLVED event here. This is done by caller
 // to resolve if appropriate.
 }
         manifestLocalization = null;
     }

     /**
      * Unload the bundle.
      * This method must be called while holding the bundles lock.
      *
      * @return true if an exported package is "in use". i.e. it has been imported by a bundle
      */
     protected boolean unload() {
         if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
             if ((state & (UNINSTALLED | INSTALLED | RESOLVED)) == 0) {
                 Debug.println("Bundle.unload called when state != UNINSTALLED | INSTALLED | RESOLVED: " + this); //$NON-NLS-1$
 Debug.printStackTrace(new Exception ("Stack trace")); //$NON-NLS-1$
 }
         }

         boolean exporting = false;

         if (framework.isActive()) {
             if (state == RESOLVED) {
                 BundleLoaderProxy curProxy = getLoaderProxy();
                 exporting = curProxy.inUse();
                 if (exporting)
                     // make sure the BundleLoader is created.
 curProxy.getBundleLoader().createClassLoader();
                 else
                     closeBundleLoader(proxy);

                 state = INSTALLED;
                 proxy = null;
                 fragments = null;
                 domain = null;
             }
         }
         if (!exporting) {
             try {
                 this.bundledata.close();
             } catch (IOException e) { // Do Nothing.
 }
         }

         return (exporting);
     }

     private BundleLoader checkLoader() {
         checkValid();

         // check to see if the bundle is resolved
 if (!isResolved()) {
             if (!framework.packageAdmin.resolveBundles(new Bundle[] {this})) {
                 return null;
             }
         }
         if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
             if ((state & (STARTING | ACTIVE | STOPPING | RESOLVED)) == 0) {
                 Debug.println("Bundle.checkLoader() called when state != STARTING | ACTIVE | STOPPING | RESOLVED: " + this); //$NON-NLS-1$
 Debug.printStackTrace(new Exception ("Stack trace")); //$NON-NLS-1$
 }
         }

         BundleLoader loader = getBundleLoader();
         if (loader == null) {
             if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                 Debug.println("Bundle.checkLoader() called when loader == null: " + this); //$NON-NLS-1$
 Debug.printStackTrace(new Exception ("Stack trace")); //$NON-NLS-1$
 }
             return null;
         }
         return loader;
     }

     /**
      * This method loads a class from the bundle.
      *
      * @param name the name of the desired Class.
      * @param checkPermission indicates whether a permission check should be done.
      * @return the resulting Class
      * @exception java.lang.ClassNotFoundException if the class definition was not found.
      */
     protected Class loadClass(String name, boolean checkPermission) throws ClassNotFoundException {
         if (checkPermission) {
             try {
                 framework.checkAdminPermission(this, AdminPermission.CLASS);
             } catch (SecurityException e) {
                 throw new ClassNotFoundException ();
             }
         }
         BundleLoader loader = checkLoader();
         if (loader == null)
             throw new ClassNotFoundException (NLS.bind(Msg.BUNDLE_CNFE_NOT_RESOLVED, name, getBundleData().getLocation()));
         try {
             return (loader.loadClass(name));
         } catch (ClassNotFoundException e) {
             // this is to support backward compatibility in eclipse
 // we always attempted to start a bundle even if the class was not found
 if (!(e instanceof StatusException) && (bundledata.getStatus() & Constants.BUNDLE_LAZY_START) != 0 && !testStateChanging(Thread.currentThread()))
                 try {
                     // only start the bundle if this is a simple CNFE
 framework.secureAction.start(this, START_TRANSIENT);
                 } catch (BundleException be) {
                     framework.adaptor.getFrameworkLog().log(new FrameworkLogEntry(FrameworkAdaptor.FRAMEWORK_SYMBOLICNAME, FrameworkLogEntry.WARNING, 0, be.getMessage(), 0, be, null));
                 }
             throw e;
         }
     }

     /**
      * Find the specified resource in this bundle.
      *
      * This bundle's class loader is called to search for the named resource.
      * If this bundle's state is <tt>INSTALLED</tt>, then only this bundle will
      * be searched for the specified resource. Imported packages cannot be searched
      * when a bundle has not been resolved.
      *
      * @param name The name of the resource.
      * See <tt>java.lang.ClassLoader.getResource</tt> for a description of
      * the format of a resource name.
      * @return a URL to the named resource, or <tt>null</tt> if the resource could
      * not be found or if the caller does not have
      * the <tt>AdminPermission</tt>, and the Java Runtime Environment supports permissions.
      *
      * @exception java.lang.IllegalStateException If this bundle has been uninstalled.
      */
     public URL getResource(String name) {
         BundleLoader loader = null;
         try {
             framework.checkAdminPermission(this, AdminPermission.RESOURCE);
         } catch (SecurityException ee) {
             return null;
         }
         loader = checkLoader();
         if (loader == null)
             return null;
         return (loader.findResource(name));
     }

     public Enumeration getResources(String name) throws IOException {
         BundleLoader loader = null;
         try {
             framework.checkAdminPermission(this, AdminPermission.RESOURCE);
         } catch (SecurityException ee) {
             return null;
         }
         loader = checkLoader();
         if (loader == null)
             return null;
         Enumeration result = loader.getResources(name);
         if (result != null && result.hasMoreElements())
             return result;
         return null;
     }

     /**
      * Internal worker to start a bundle.
      *
      * @param options the start options
      */
     protected void startWorker(int options) throws BundleException {
         if ((options & START_TRANSIENT) == 0) {
             setStatus(Constants.BUNDLE_STARTED, true);
             setStatus(Constants.BUNDLE_ACTIVATION_POLICY, (options & START_ACTIVATION_POLICY) != 0);
             if (Debug.DEBUG && Debug.MONITOR_ACTIVATION)
                 new Exception ("A persistent start has been called on bundle: " + getBundleData()).printStackTrace();
         }
         if (!framework.active || (state & ACTIVE) != 0)
             return;

         if (state == INSTALLED) {
             if (!framework.packageAdmin.resolveBundles(new Bundle[] {this}))
                 throw new BundleException(getResolutionFailureMessage());
         }

         if (getStartLevel() > framework.startLevelManager.getStartLevel()){
             if ((options & START_TRANSIENT) != 0) {
                 // throw exception if this is a transient start
 String msg = NLS.bind(Msg.BUNDLE_TRANSIENT_START_ERROR, this);
                 // Use a StatusException to indicate to the lazy starter that this should result in a warning
 throw new BundleException(msg, new BundleStatusException(msg, StatusException.CODE_WARNING, this));
             }
             return;
         }
         if ((options & START_ACTIVATION_POLICY) != 0 && (state & STARTING) == 0) {
             // the bundle must use the activation policy here.
 if ((bundledata.getStatus() & Constants.BUNDLE_LAZY_START) != 0) {
                 // now we must publish the LAZY_ACTIVATION event and return
 state = STARTING;
                 framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this);
                 return;
             }
         }

         if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
             Debug.println("Bundle: Active sl = " + framework.startLevelManager.getStartLevel() + "; Bundle " + getBundleId() + " sl = " + getStartLevel()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
 }

         state = STARTING;
         framework.publishBundleEvent(BundleEvent.STARTING, this);
         context = getContext();
         //STARTUP TIMING Start here
 long start = 0;
         if (Debug.DEBUG) {
             BundleWatcher bundleStats = framework.adaptor.getBundleWatcher();
             if (bundleStats != null)
                 bundleStats.watchBundle(this, BundleWatcher.START_ACTIVATION);
             if (Debug.DEBUG_BUNDLE_TIME) {
                 start = System.currentTimeMillis();
                 System.out.println("Starting " + getSymbolicName()); //$NON-NLS-1$
 }
         }
         try {
             context.start();

             if (framework.active) {
                 state = ACTIVE;

                 if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                     Debug.println("->started " + this); //$NON-NLS-1$
 }

                 framework.publishBundleEvent(BundleEvent.STARTED, this);
             }

         } catch (BundleException e) {
             // we must fire the stopping event
 state = STOPPING;
             framework.publishBundleEvent(BundleEvent.STOPPING, this);

             context.close();
             context = null;

             state = RESOLVED;
             // if this is a lazy start bundle that fails to start then
 // we must fire the stopped event
 framework.publishBundleEvent(BundleEvent.STOPPED, this);
             throw e;
         } finally {
             if (Debug.DEBUG) {
                 BundleWatcher bundleStats = framework.adaptor.getBundleWatcher();
                 if (bundleStats != null)
                     bundleStats.watchBundle(this, BundleWatcher.END_ACTIVATION);
                 if (Debug.DEBUG_BUNDLE_TIME)
                     System.out.println("End starting " + getSymbolicName() + " " + (System.currentTimeMillis() - start)); //$NON-NLS-1$ //$NON-NLS-2$
 }
         }

         if (state == UNINSTALLED) {
             context.close();
             context = null;
             throw new BundleException(NLS.bind(Msg.BUNDLE_UNINSTALLED_EXCEPTION, getBundleData().getLocation()));
         }
     }

     protected boolean readyToResume() {
         // Return false if the bundle is not at the correct start-level
 if (getStartLevel() > framework.startLevelManager.getStartLevel())
             return false;
         int status = bundledata.getStatus();
         // Return false if the bundle is not persistently marked for start
 if ((status & Constants.BUNDLE_STARTED) == 0)
             return false;
         if ((status & Constants.BUNDLE_ACTIVATION_POLICY) == 0 || (status & Constants.BUNDLE_LAZY_START) == 0)
             return true;
         if (!isResolved())
             // should never transition from UNRESOLVED -> STARTING
 return false;
         // now we can publish the LAZY_ACTIVATION event
 state = STARTING;
         framework.publishBundleEvent(BundleEvent.LAZY_ACTIVATION, this);
         return false;
     }

     /**
      * Create a BundleContext for this bundle.
      *
      * @return BundleContext for this bundle.
      */
     protected BundleContextImpl createContext() {
         return (new BundleContextImpl(this));
     }

     /**
      * Return the current context for this bundle.
      *
      * @return BundleContext for this bundle.
      */
     protected synchronized BundleContextImpl getContext() {
         if (context == null) {
             // only create the context if we are starting, active or stopping
 // this is so that SCR can get the context for lazy-start bundles
 if ((state & (STARTING | ACTIVE | STOPPING)) != 0)
                 context = createContext();
         }
         return (context);
     }

     /**
      * Internal worker to stop a bundle.
      *
      * @param options the stop options
      */
     protected void stopWorker(int options) throws BundleException {
         if ((options & STOP_TRANSIENT) == 0) {
             setStatus(Constants.BUNDLE_STARTED, false);
             setStatus(Constants.BUNDLE_ACTIVATION_POLICY, false);
             if (Debug.DEBUG && Debug.MONITOR_ACTIVATION)
                 new Exception ("A persistent start has been called on bundle: " + getBundleData()).printStackTrace();
         }
         if (framework.active) {
             if ((state & (STOPPING | RESOLVED | INSTALLED)) != 0) {
                 return;
             }
             if (Debug.DEBUG) {
                 BundleWatcher bundleStats = framework.adaptor.getBundleWatcher();
                 if (bundleStats != null)
                     bundleStats.watchBundle(this, BundleWatcher.START_DEACTIVATION);
             }
             state = STOPPING;
             framework.publishBundleEvent(BundleEvent.STOPPING, this);
             try {
                 // context may be null if a lazy-start bundle is STARTING
 if (context != null)
                     context.stop();
             } finally {
                 if (context != null) {
                     context.close();
                     context = null;
                 }

                 checkValid();

                 state = RESOLVED;

                 if (Debug.DEBUG && Debug.DEBUG_GENERAL) {
                     Debug.println("->stopped " + this); //$NON-NLS-1$
 }

                 framework.publishBundleEvent(BundleEvent.STOPPED, this);
                 if (Debug.DEBUG) {
                     BundleWatcher bundleStats = framework.adaptor.getBundleWatcher();
                     if (bundleStats != null)
                         bundleStats.watchBundle(this, BundleWatcher.END_DEACTIVATION);
                 }
             }
         }
     }

     /**
      * Provides a list of {@link ServiceReferenceImpl}s for the services
      * registered by this bundle
      * or <code>null</code> if the bundle has no registered
      * services.
      *
      * <p>The list is valid at the time
      * of the call to this method, but the framework is a very dynamic
      * environment and services can be modified or unregistered at anytime.
      *
      * @return An array of {@link ServiceReferenceImpl} or <code>null</code>.
      * @exception java.lang.IllegalStateException If the
      * bundle has been uninstalled.
      * @see ServiceRegistrationImpl
      * @see ServiceReferenceImpl
      */
     public org.osgi.framework.ServiceReference[] getRegisteredServices() {
         checkValid();

         if (context == null) {
             return (null);
         }

         return (context.getRegisteredServices());
     }

     /**
      * Provides a list of {@link ServiceReferenceImpl}s for the
      * services this bundle is using,
      * or <code>null</code> if the bundle is not using any services.
      * A bundle is considered to be using a service if the bundle's
      * use count for the service is greater than zero.
      *
      * <p>The list is valid at the time
      * of the call to this method, but the framework is a very dynamic
      * environment and services can be modified or unregistered at anytime.
      *
      * @return An array of {@link ServiceReferenceImpl} or <code>null</code>.
      * @exception java.lang.IllegalStateException If the
      * bundle has been uninstalled.
      * @see ServiceReferenceImpl
      */
     public org.osgi.framework.ServiceReference[] getServicesInUse() {
         checkValid();

         if (context == null) {
             return (null);
         }

         return (context.getServicesInUse());
     }

     protected Bundle[] getFragments() {
         synchronized (framework.bundles) {
             if (fragments == null)
                 return null;
             Bundle[] result = new Bundle[fragments.length];
             System.arraycopy(fragments, 0, result, 0, result.length);
             return result;
         }
     }

     /**
      * Attaches a fragment to this BundleHost. Fragments must be attached to
      * the host by ID order. If the ClassLoader of the host is already created
      * then the fragment must be attached to the host ClassLoader
      * @param fragment The fragment bundle to attach
      * return true if the fragment successfully attached; false if the fragment
      * could not be logically inserted at the end of the fragment chain.
      */
     protected void attachFragment(BundleFragment fragment) throws BundleException {
         // do not force the creation of the bundle loader here
 BundleLoader loader = getLoaderProxy().getBasicBundleLoader();
         // If the Host ClassLoader exists then we must attach
 // the fragment to the ClassLoader.
 if (loader != null)
             loader.attachFragment(fragment);

         if (fragments == null) {
             fragments = new BundleFragment[] {fragment};
         } else {
             boolean inserted = false;
             // We must keep our fragments ordered by bundle ID; or
 // install order.
 BundleFragment[] newFragments = new BundleFragment[fragments.length + 1];
             for (int i = 0; i < fragments.length; i++) {
                 if (fragment == fragments[i])
                     return; // this fragment is already attached
 if (!inserted && fragment.getBundleId() < fragments[i].getBundleId()) {
                     // if the loader has already been created
 // then we cannot attach a fragment into the middle
 // of the fragment chain.
 if (loader != null) {
                         throw new BundleException(NLS.bind(Msg.BUNDLE_LOADER_ATTACHMENT_ERROR, fragments[i].getSymbolicName(), getSymbolicName()));
                     }
                     newFragments[i] = fragment;
                     inserted = true;
                 }
                 newFragments[inserted ? i + 1 : i] = fragments[i];
             }
             if (!inserted)
                 newFragments[newFragments.length - 1] = fragment;
             fragments = newFragments;
         }
     }

     protected BundleLoader getBundleLoader() {
         BundleLoaderProxy curProxy = getLoaderProxy();
         return curProxy == null ? null : curProxy.getBundleLoader();
     }

     protected synchronized BundleLoaderProxy getLoaderProxy() {
         if (proxy != null)
             return proxy;
         BundleDescription bundleDescription = getBundleDescription();
         if (bundleDescription == null)
             return null;
         proxy = new BundleLoaderProxy(this, bundleDescription);
         bundleDescription.setUserObject(proxy);
         return proxy;
     }

     static void closeBundleLoader(BundleLoaderProxy proxy) {
         if (proxy == null)
             return;
         // First close the BundleLoader
 BundleLoader loader = proxy.getBasicBundleLoader();
         if (loader != null)
             loader.close();
         proxy.setStale();
         // if proxy is not null then make sure to unset user object
 // associated with the proxy in the state
 BundleDescription description = proxy.getBundleDescription();
         description.setUserObject(null);
     }
 }

